﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Web.UI;
using Succell.Dnn.JQuery.UI;

namespace Succell.Dnn.JQuery.Extras
{
    /// <summary>
    /// Creates a <see cref="WindowedDialog"/> control.
    /// </summary>
    [ParseChildren(true)]
    public class WindowedDialog : Control
    {
        #region properties

        /// <summary>
        /// Gets or sets the name of the theme to use for the control.
        /// </summary>
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        public string ThemeName { get; set; }

        #endregion

        #region jQuery UI settings properties

        /// <summary>
        /// Gets or sets whether the dialog will open automatically when the page loads.
        /// If <c>false</c> it will stay hidden until a client event opens it.
        /// </summary>
        [Setting("autoOpen")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [DefaultValue(false)]
        public bool AutoOpen { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will open in the center of the screen.
        /// If <c>false</c> it will stay hidden until a client event opens it.
        /// </summary>
        [Setting("centerScreen")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(false)]
        public bool CenterScreen { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will close automatically when the parent window is closed.
        /// If <c>false</c> it will remain open until it is closed manually.
        /// </summary>
        [Setting("dependant")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [DefaultValue(false)]
        public bool Dependant { get; set; }

        /// <summary>
        /// Gets or sets the frame name for the dialog.
        /// </summary>
        [Setting("frameName")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [DefaultValue(null)]
        public string FrameName { get; set; }

        private int dialogHeight = 300;

        /// <summary>
        /// Gets or sets the height for the dialog.
        /// </summary>
        [Setting("height")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(300)]
        public int DialogHeight
        {
            get { return this.dialogHeight; }
            set { this.dialogHeight = value; }
        }

        private int dialogWidth = 400;

        /// <summary>
        /// Gets or sets the width for the dialog.
        /// </summary>
        [Setting("width")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(400)]
        public int DialogWidth
        {
            get { return this.dialogWidth; }
            set { this.dialogWidth = value; }
        }

        /// <summary>
        /// Gets or sets the left offset of the window when it is opened.
        /// A value of <c>null</c> will automatically position the dialog.
        /// </summary>
        [Setting("left")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(null)]
        public int? Left { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will show the location bar.
        /// </summary>
        [Setting("location")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(false)]
        public bool Location { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will show the menu bar.
        /// </summary>
        [Setting("menuBar")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(false)]
        public bool MenuBar { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will be modal.
        /// </summary>
        [Setting("modal")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(false)]
        public bool Modal { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will be resizable.
        /// </summary>
        [Setting("resizable")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [DefaultValue(false)]
        public bool Resizable { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will show scrollbars.
        /// </summary>
        [Setting("scrollbars")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [DefaultValue(false)]
        public bool Scrollbars { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will show the status bar.
        /// </summary>
        [Setting("status")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(false)]
        public bool Status { get; set; }

        /// <summary>
        /// Gets or sets whether the dialog will show the toolbar.
        /// </summary>
        [Setting("toolbar")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(false)]
        public bool Toolbar { get; set; }

        /// <summary>
        /// Gets or sets the top offset of the window when it is opened.
        /// A value of <c>null</c> will automatically position the dialog.
        /// </summary>
        [Setting("top")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Appearance")]
        [DefaultValue(null)]
        public int? Top { get; set; }

        private string url = "about:blank";

        /// <summary>
        /// Gets or sets the frame name for the dialog.
        /// </summary>
        [Setting("url")]
        [Browsable(true)]
        [Localizable(false)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [DefaultValue("about:blank")]
        public string Url
        {
            get { return this.url; }
            set { this.url = value; }
        }

        #endregion

        #region jQuery client events

        /// <summary>
        /// Defines the client events for the <see cref="WindowedDialog"/> control.
        /// </summary>
        public sealed class ClientEventDefinitions
        {
            #region properties

            [Setting("beforeClose")]
            internal ClientEventHandler BeforeCloseHandler
            {
                get { return new ClientEventHandler("event,ui", this.BeforeClose); }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that is triggered when a dialog attempts to close.
            /// If the beforeclose event handler returns false, the close will be
            /// prevented. The names of the arguments to the function are <c>event</c> and <c>ui</c>.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string BeforeClose
            {
                get;
                set;
            }

            [Setting("close")]
            internal ClientEventHandler CloseHandler
            {
                get
                {
                    return new ClientEventHandler("event,ui", this.Close);
                }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that is triggered when dialog is closed.
            /// The name of the arguments to the function are <c>event</c> and <c>ui</c>.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string Close
            {
                get;
                set;
            }

            [Setting("result")]
            internal ClientEventHandler ResultHandler
            {
                get
                {
                    return new ClientEventHandler("event,ui", this.Result);
                }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that is triggered when the dialog is closed
            /// with a result. The names of the arguments to the function are <c>event</c> and
            /// <c>ui</c>.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string Result
            {
                get;
                set;
            }

            [Setting("open")]
            internal ClientEventHandler OpenHandler
            {
                get
                {
                    return new ClientEventHandler("event,ui", this.Open);
                }
            }

            /// <summary>
            /// Gets or sets a javascript snippet, that is triggered when dialog is opened.
            /// The name of the arguments to the function are <c>event</c> and <c>ui</c>.
            /// </summary>
            [Browsable(true)]
            [Localizable(false)]
            [EditorBrowsable(EditorBrowsableState.Always)]
            [Category("Behavior")]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public string Open
            {
                get;
                set;
            }

            #endregion

            #region constructors

            /// <summary>
            /// Creates a new <see cref="ClientEventDefinitions"/> instance.
            /// </summary>
            internal ClientEventDefinitions()
            {
            }

            #endregion
        }

        private readonly ClientEventDefinitions clientEvents = new ClientEventDefinitions();

        /// <summary>
        /// Holds the definitions for the client events.
        /// </summary>
        [SettingGroup]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Browsable(true)]
        [Localizable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        public ClientEventDefinitions ClientEvents
        {
            get
            {
                return this.clientEvents;
            }
        }

        #endregion

        #region jQuery client methods

        /// <summary>
        /// Defines the client methods for the <see cref="WindowedDialog"/> control.
        /// </summary>
        public class ClientMethodDefinitions
        {
            #region fields

            private readonly WindowedDialog owner;

            #endregion

            #region constructors

            internal ClientMethodDefinitions(WindowedDialog owner)
            {
                this.owner = owner;
            }

            #endregion

            #region methods

            private string GenerateMethodCall(string methodName)
            {
                return String.Format(CultureInfo.InvariantCulture, "jQuery.ui.windowedDialog.{0}('{1}')",
                                     methodName, this.owner.FrameName);
            }

            /// <summary>
            /// Gets a javascript snippet for the client close method.
            /// </summary>
            /// <returns>A javascript snippet for the client close method.</returns>
            public string Close()
            {
                return this.GenerateMethodCall("close");
            }

            /// <summary>
            /// Gets a javascript snippet for the client destroy method.
            /// </summary>
            /// <returns>A javascript snippet for the client destroy method.</returns>
            public string Destroy()
            {
                return this.GenerateMethodCall("destroy");
            }

            /// <summary>
            /// Gets a javascript snippet for the client open method.
            /// </summary>
            /// <returns>A javascript snippet for the client open method.</returns>
            public string Open()
            {
                return this.GenerateMethodCall("open");
            }

            #endregion
        }

        private ClientMethodDefinitions clientMethods;

        /// <summary>
        /// Gets a factory for creating javascript snippets for the <see cref="Dialog"/>'s
        /// client methods for use in event handlers.
        /// </summary>
        public ClientMethodDefinitions ClientMethods
        {
            get
            {
                if (null == this.clientMethods)
                {
                    this.clientMethods = new ClientMethodDefinitions(this);
                }
                return this.clientMethods;
            }
        }

        #endregion

        #region events

        /// <summary>
        /// This event is raised, just before the control applies its javascript
        /// settings to the initialization code. You can use it to manipulate the
        /// settings dictionary.
        /// </summary>
        /// <remarks>
        /// Be careful when using this event. It allows you to reach deeply into
        /// the internal workings of the control, possibly breaking functionality.
        /// </remarks>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [Localizable(false)]
        [Category("Behavior")]
        public event EventHandler<SettingsEventArgs> ApplyingSettings;

        /// <summary>
        /// Fires the <see cref="ApplyingSettings"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SettingsEventArgs"/> passed to the event.</param>
        protected virtual void OnApplyingSettings(SettingsEventArgs e)
        {
            if (null != this.ApplyingSettings)
            {
                this.ApplyingSettings(this, e);
            }
        }

        #endregion

        #region page lifecycle

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            this.Page.RegisterJQueryUI();

            base.OnInit(e);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (this.Visible)
            {
                var settings = SettingSerializer.FetchSettings(this);

                if (this.Modal)
                {
                    // handle theme wrapping
                    string themeName = this.ResolveThemeName();
                    if (!String.IsNullOrEmpty(themeName))
                    {
                        //// NOTE: 'this' is the actual content control of the dialog, hence the need to get the parent's parent!
                        //settings["open"] =
                        //    new ClientEventHandler(this.ClientEvents.OpenHandler,
                        //                           String.Format(CultureInfo.InvariantCulture,
                        //                                         "jQuery(ui.overlay.$el).wrap('<div class=\"jquery-ui-{0}\"></div>');",
                        //                                         themeName));
                        //settings["close"] =
                        //    new ClientEventHandler(this.ClientEvents.CloseHandler,
                        //                           "jQuery(ui.overlay.$el).parent().replaceWith(jQuery(ui.overlay));");
                        settings["theme"] = "jquery-ui-" + themeName;
                    }
                }

                // apply custom setting manipulation logic
                this.OnApplyingSettings(new SettingsEventArgs(settings));

                // make it json and register the DocumentReadyBlock
                string settingsJson = SettingSerializer.Serialize(settings);
                this.Page.ClientScript.RegisterClientScriptInclude(typeof(WindowedDialog), "jquery-ui.windowedDialog", this.ResolveUrl("~/DesktopModules/Succell.JQuery.Extras/js/jquery-ui.windowedDialog.0.5.1.min.js"));
                this.Page.RegisterJQueryOnDocumentReadyBlock(
                    String.Format(
                        CultureInfo.InvariantCulture,
                        "$(window).windowedDialog({0})",
                        settingsJson
                        ));
            }
        }

        /// <summary>
        /// Renders the <see cref="WindowedDialog"/> control to the specified <see cref="T:System.Web.UI.HtmlTextWriter"/> object.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> that receives the rendered output.</param>
        protected override void Render(HtmlTextWriter writer)
        {
        }

        #endregion

        #region methods

        private string ResolveThemeName()
        {
            string themeName = this.ThemeName;
            if (String.IsNullOrEmpty(themeName))
            {
                themeName = this.Page.GetDefaultJQueryUITheme();
            }
            return themeName;
        }

        #endregion
    }
}
